module Aspect {
    export class WeaveEvent {
        constructor (public name: string, public method: string, public error: Error = null) { }

        toString(): string {
            var eventString = 'Event: ' + this.name + '. Method: ' + this.method + '.';
            if (this.error) {
                eventString += ' Error: ' + this.error.name + ' ' + this.error.message + '.';
            }
            return eventString;
        }
    }

    export class Weaver {
        // Will fire before the target is called
        static before(target: Function, method: string, action: Function): void {
            var old = target.prototype[method];
            var event = new WeaveEvent('before', method);

            target.prototype[method] = function {
                var args = Array.prototype.slice.call(arguments);
                action.apply(this, [event, args]);
                var result = old.apply(this, args);
                return result;
            }
        }

        // Will fire after the target is called successfully
        static after(target: Function, method: string, action: Function): void {
            var old = target.prototype[method];
            var event = new WeaveEvent('after', method);

            target.prototype[method] = function {
                var args = Array.prototype.slice.call(arguments);
                var result = old.apply(this, args);
                action.apply(this, [event, args]);
                return result;
            }
        }

        // Will fire after the target is called, whether the call was successful or not
        static afterAll(target: Function, method: string, action: Function): void {
            var old = target.prototype[method];
            var event = new WeaveEvent('afterAll', method);

            target.prototype[method] = function {
                var args = Array.prototype.slice.call(arguments);
                try {
                    var result = old.apply(this, args);
                    return result;
                } finally {
                    action.apply(this, [event, args]);
                }
            }
        }

        // Will fire if the target fails
        static error(target: Function, method: string, action: Function): void {
            var old = target.prototype[method];

            target.prototype[method] = function {
                var args = Array.prototype.slice.call(arguments);
                try {
                    var result = old.apply(this, args);
                    return result;
                } catch (ex) {
                    var event = new WeaveEvent('error', method, ex);
                    action.apply(this, [event, args]);
                    throw ex;
                }
            }
        }
    }
}